rails-bootstrap-markdown 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec
4
+
5
+ gem 'rake'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Danny Tatom
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,45 @@
1
+ # Rails::Bootstrap::Markdown
2
+
3
+ A rails gem for [Bootstrap Markdown](http://toopay.github.io/bootstrap-markdown/), rewritten to SCSS for use with [bootstrap-sass](https://github.com/thomas-mcdonald/bootstrap-sass)
4
+
5
+ 3rd party libraries included:
6
+
7
+ - http://toopay.github.io/bootstrap-markdown/
8
+ - https://github.com/domchristie/to-markdown
9
+ - https://github.com/evilstreak/markdown-js
10
+
11
+ ## Installation
12
+
13
+ Add this line to your application's Gemfile:
14
+
15
+ gem 'rails-bootstrap-markdown'
16
+
17
+ And then execute:
18
+
19
+ $ bundle
20
+
21
+ Or install it yourself as:
22
+
23
+ $ gem install rails-bootstrap-markdown
24
+
25
+ ## Usage
26
+
27
+ ### CSS
28
+
29
+ ```
30
+ *= require bootstrap-markdown
31
+ ```
32
+
33
+ ### Javascript
34
+
35
+ ```
36
+ //= requrie bootstrap-markdown
37
+ ```
38
+
39
+ ## Contributing
40
+
41
+ 1. Fork it
42
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
43
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
44
+ 4. Push to the branch (`git push origin my-new-feature`)
45
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1 @@
1
+ require "bundler/gem_tasks"
@@ -0,0 +1,989 @@
1
+ /* ===================================================
2
+ * bootstrap-markdown.js v1.0.0
3
+ * http://github.com/toopay/bootstrap-markdown
4
+ * ===================================================
5
+ * Copyright 2013 Taufan Aditya
6
+ *
7
+ * Licensed under the Apache License, Version 2.0 (the "License");
8
+ * you may not use this file except in compliance with the License.
9
+ * You may obtain a copy of the License at
10
+ *
11
+ * http://www.apache.org/licenses/LICENSE-2.0
12
+ *
13
+ * Unless required by applicable law or agreed to in writing, software
14
+ * distributed under the License is distributed on an "AS IS" BASIS,
15
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16
+ * See the License for the specific language governing permissions and
17
+ * limitations under the License.
18
+ * ========================================================== */
19
+
20
+ //= require markdown
21
+ //= require to-markdown
22
+
23
+ !function ($) {
24
+
25
+ "use strict"; // jshint ;_;
26
+
27
+
28
+ /* MARKDOWN CLASS DEFINITION
29
+ * ========================== */
30
+
31
+ var Markdown = function (element, options) {
32
+ // Class Properties
33
+ this.$ns = 'bootstrap-markdown'
34
+ this.$element = $(element)
35
+ this.$editable = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
36
+ this.$cloneEditor = {el:null, type:null,attrKeys:[], attrValues:[], content:null}
37
+ this.$options = $.extend(true, {}, $.fn.markdown.defaults, options)
38
+ this.$oldContent = null
39
+ this.$isPreview = false
40
+ this.$editor = null
41
+ this.$textarea = null
42
+ this.$handler = []
43
+ this.$callback = []
44
+ this.$nextTab = []
45
+
46
+ this.showEditor()
47
+ }
48
+
49
+ Markdown.prototype = {
50
+
51
+ constructor: Markdown
52
+
53
+ , __alterButtons: function(name,alter) {
54
+ var handler = this.$handler, isAll = (name == 'all'),that = this
55
+
56
+ $.each(handler,function(k,v) {
57
+ var halt = true
58
+ if (isAll) {
59
+ halt = false
60
+ } else {
61
+ halt = v.indexOf(name) < 0
62
+ }
63
+
64
+ if (halt == false) {
65
+ alter(that.$editor.find('button[data-handler="'+v+'"]'))
66
+ }
67
+ })
68
+ }
69
+
70
+ , __buildButtons: function(buttonsArray, container) {
71
+ var i,
72
+ ns = this.$ns,
73
+ handler = this.$handler,
74
+ callback = this.$callback
75
+
76
+ for (i=0;i<buttonsArray.length;i++) {
77
+ // Build each group container
78
+ var y, btnGroups = buttonsArray[i]
79
+ for (y=0;y<btnGroups.length;y++) {
80
+ // Build each button group
81
+ var z,
82
+ buttons = btnGroups[y].data,
83
+ btnGroupContainer = $('<div/>', {
84
+ 'class': 'btn-group'
85
+ })
86
+
87
+ for (z=0;z<buttons.length;z++) {
88
+ var button = buttons[z],
89
+ buttonHandler = ns+'-'+button.name,
90
+ btnText = button.btnText ? button.btnText : '',
91
+ btnClass = button.btnClass ? button.btnClass : 'btn'
92
+
93
+ // Attach the button object
94
+ btnGroupContainer.append('<button class="'
95
+ +btnClass
96
+ +' btn-small" title="'
97
+ +button.title
98
+ +'" data-provider="'
99
+ +ns
100
+ +'" data-handler="'
101
+ +buttonHandler
102
+ +'"><i class="'
103
+ +button.icon
104
+ +'"></i> '
105
+ +btnText
106
+ +'</button>')
107
+
108
+ // Register handler and callback
109
+ handler.push(buttonHandler)
110
+ callback.push(button.callback)
111
+ }
112
+
113
+ // Attach the button group into container dom
114
+ container.append(btnGroupContainer)
115
+ }
116
+ }
117
+
118
+ return container
119
+ }
120
+ , __setListener: function() {
121
+ // Set size and resizable Properties
122
+ var hasRows = typeof this.$textarea.attr('rows') != 'undefined',
123
+ maxRows = this.$textarea.val().split("\n").length > 5 ? this.$textarea.val().split("\n").length : '5',
124
+ rowsVal = hasRows ? this.$textarea.attr('rows') : maxRows
125
+
126
+ this.$textarea.attr('rows',rowsVal)
127
+ this.$textarea.css('resize','none')
128
+
129
+ this.$textarea
130
+ .on('focus', $.proxy(this.focus, this))
131
+ .on('keypress', $.proxy(this.keypress, this))
132
+ .on('keyup', $.proxy(this.keyup, this))
133
+
134
+ if (this.eventSupported('keydown')) {
135
+ this.$textarea.on('keydown', $.proxy(this.keydown, this))
136
+ }
137
+
138
+ // Re-attach markdown data
139
+ this.$textarea.data('markdown',this)
140
+ }
141
+
142
+ , __handle: function(e) {
143
+ var target = $(e.currentTarget),
144
+ handler = this.$handler,
145
+ callback = this.$callback,
146
+ handlerName = target.attr('data-handler'),
147
+ callbackIndex = handler.indexOf(handlerName),
148
+ callbackHandler = callback[callbackIndex]
149
+
150
+ // Trigger the focusin
151
+ $(e.currentTarget).focus()
152
+
153
+ callbackHandler(this)
154
+
155
+ // Unless it was the save handler,
156
+ // focusin the textarea
157
+ if (handlerName.indexOf('cmdSave') < 0) {
158
+ this.$textarea.focus()
159
+ }
160
+
161
+ e.preventDefault()
162
+ }
163
+
164
+ , showEditor: function() {
165
+ var instance = this,
166
+ textarea,
167
+ ns = this.$ns,
168
+ container = this.$element,
169
+ originalHeigth = container.css('height'),
170
+ originalWidth = container.css('width'),
171
+ editable = this.$editable,
172
+ handler = this.$handler,
173
+ callback = this.$callback,
174
+ options = this.$options,
175
+ editor = $( '<div/>', {
176
+ 'class': 'md-editor',
177
+ click: function() {
178
+ instance.focus()
179
+ }
180
+ })
181
+
182
+ // Prepare the editor
183
+ if (this.$editor == null) {
184
+ // Create the panel
185
+ var editorHeader = $('<div/>', {
186
+ 'class': 'md-header'
187
+ })
188
+
189
+ // Build the main buttons
190
+ if (options.buttons.length > 0) {
191
+ editorHeader = this.__buildButtons(options.buttons, editorHeader)
192
+ }
193
+
194
+ // Build the additional buttons
195
+ if (options.additionalButtons.length > 0) {
196
+ editorHeader = this.__buildButtons(options.additionalButtons, editorHeader)
197
+ }
198
+
199
+ editor.append(editorHeader)
200
+
201
+ // Wrap the textarea
202
+ if (container.is('textarea')) {
203
+ container.before(editor)
204
+ textarea = container
205
+ textarea.addClass('md-input')
206
+ editor.append(textarea)
207
+ } else {
208
+ var rawContent = (typeof toMarkdown == 'function') ? toMarkdown(container.html()) : container.html(),
209
+ currentContent = $.trim(rawContent)
210
+
211
+ // This is some arbitrary content that could be edited
212
+ textarea = $('<textarea/>', {
213
+ 'class': 'md-input',
214
+ 'val' : currentContent
215
+ })
216
+
217
+ editor.append(textarea)
218
+
219
+ // Save the editable
220
+ editable.el = container
221
+ editable.type = container.prop('tagName').toLowerCase()
222
+ editable.content = container.html()
223
+
224
+ $(container[0].attributes).each(function(){
225
+ editable.attrKeys.push(this.nodeName)
226
+ editable.attrValues.push(this.nodeValue)
227
+ })
228
+
229
+ // Set editor to blocked the original container
230
+ container.replaceWith(editor)
231
+ }
232
+
233
+ // Create the footer if savable
234
+ if (options.savable) {
235
+ var editorFooter = $('<div/>', {
236
+ 'class': 'md-footer'
237
+ }),
238
+ saveHandler = 'cmdSave'
239
+
240
+ // Register handler and callback
241
+ handler.push(saveHandler)
242
+ callback.push(options.onSave)
243
+
244
+ editorFooter.append('<button class="btn btn-success" data-provider="'
245
+ +ns
246
+ +'" data-handler="'
247
+ +saveHandler
248
+ +'"><i class="icon icon-white icon-ok"></i> Save</button>')
249
+
250
+ editor.append(editorFooter)
251
+ }
252
+
253
+ // Set width/height
254
+ $.each(['height','width'],function(k,attr){
255
+ if (options[attr] != 'inherit') {
256
+ if (jQuery.isNumeric(options[attr])) {
257
+ editor.css(attr,options[attr]+'px')
258
+ } else {
259
+ editor.addClass(options[attr])
260
+ }
261
+ }
262
+ })
263
+
264
+ // Reference
265
+ this.$editor = editor
266
+ this.$textarea = textarea
267
+ this.$editable = editable
268
+ this.$oldContent = this.getContent()
269
+
270
+ this.__setListener()
271
+
272
+ // Set editor attributes, data short-hand API and listener
273
+ this.$editor.attr('id',(new Date).getTime())
274
+ this.$editor.on('click', '[data-provider="bootstrap-markdown"]', $.proxy(this.__handle, this))
275
+
276
+ } else {
277
+ this.$editor.show()
278
+ }
279
+
280
+ if (options.autofocus) {
281
+ this.$textarea.focus()
282
+ this.$editor.addClass('active')
283
+ }
284
+
285
+ // Trigger the onShow hook
286
+ options.onShow(this)
287
+
288
+ return this
289
+ }
290
+
291
+ , showPreview: function() {
292
+ var options = this.$options,
293
+ callbackContent = options.onPreview(this), // Try to get the content from callback
294
+ container = this.$textarea,
295
+ replacementContainer = $('<div/>',{'class':'md-preview','data-provider':'markdown-preview'}),
296
+ cloneEditor = this.$cloneEditor,
297
+ content
298
+
299
+ // Give flag that tell the editor enter preview mode
300
+ this.$isPreview = true
301
+ // Disable all buttons
302
+ this.disableButtons('all').enableButtons('cmdPreview')
303
+
304
+ // Save the editor
305
+ cloneEditor.el = container
306
+ cloneEditor.type = container.prop('tagName').toLowerCase()
307
+ cloneEditor.content = container.val()
308
+
309
+ $(container[0].attributes).each(function(){
310
+ cloneEditor.attrKeys.push(this.nodeName)
311
+ cloneEditor.attrValues.push(this.nodeValue)
312
+ })
313
+
314
+ this.$cloneEditor = cloneEditor
315
+
316
+ if (typeof callbackContent == 'string') {
317
+ // Set the content based by callback content
318
+ content = callbackContent
319
+ } else {
320
+ // Set the content
321
+ content = (typeof markdown == 'object') ? markdown.toHTML(container.val()) : container.val()
322
+ }
323
+
324
+ // Build preview element and replace the editor temporarily
325
+ replacementContainer.html(content)
326
+ container.replaceWith(replacementContainer)
327
+
328
+ // Attach the editor instances
329
+ replacementContainer.data('markdown',this)
330
+
331
+ return this
332
+ }
333
+
334
+ , hidePreview: function() {
335
+ // Give flag that tell the editor quit preview mode
336
+ this.$isPreview = false
337
+
338
+ // Build the original element
339
+ var container = this.$editor.find('div[data-provider="markdown-preview"]'),
340
+ cloneEditor = this.$cloneEditor,
341
+ oldElement = $('<'+cloneEditor.type+'/>')
342
+
343
+ $(cloneEditor.attrKeys).each(function(k,v) {
344
+ oldElement.attr(cloneEditor.attrKeys[k],cloneEditor.attrValues[k])
345
+ })
346
+
347
+ // Set the editor content
348
+ oldElement.val(cloneEditor.content)
349
+
350
+ // Set the editor data
351
+ container.replaceWith(oldElement)
352
+
353
+ // Enable all buttons
354
+ this.enableButtons('all')
355
+
356
+ // Back to the editor
357
+ this.$textarea = oldElement
358
+ this.__setListener()
359
+
360
+ return this
361
+ }
362
+
363
+ , isDirty: function() {
364
+ return this.$oldContent != this.getContent()
365
+ }
366
+
367
+ , getContent: function() {
368
+ return (this.$isPreview) ? this.$cloneEditor.content : this.$textarea.val()
369
+ }
370
+
371
+ , setContent: function(content) {
372
+ this.$textarea.val(content)
373
+
374
+ return this
375
+ }
376
+
377
+ , findSelection: function(chunk) {
378
+ var content = this.getContent(), startChunkPosition
379
+
380
+ if (startChunkPosition = content.indexOf(chunk), startChunkPosition >= 0 && chunk.length > 0) {
381
+ var oldSelection = this.getSelection(), selection
382
+
383
+ this.setSelection(startChunkPosition,startChunkPosition+chunk.length)
384
+ selection = this.getSelection()
385
+
386
+ this.setSelection(oldSelection.start,oldSelection.end)
387
+
388
+ return selection
389
+ } else {
390
+ return null
391
+ }
392
+ }
393
+
394
+ , getSelection: function() {
395
+
396
+ var e = this.$textarea[0]
397
+
398
+ return (
399
+
400
+ ('selectionStart' in e && function() {
401
+ var l = e.selectionEnd - e.selectionStart
402
+ return { start: e.selectionStart, end: e.selectionEnd, length: l, text: e.value.substr(e.selectionStart, l) }
403
+ }) ||
404
+
405
+ /* browser not supported */
406
+ function() {
407
+ return null
408
+ }
409
+
410
+ )()
411
+
412
+ }
413
+
414
+ , setSelection: function(start,end) {
415
+
416
+ var e = this.$textarea[0]
417
+
418
+ return (
419
+
420
+ ('selectionStart' in e && function() {
421
+ e.selectionStart = start
422
+ e.selectionEnd = end
423
+ return
424
+ }) ||
425
+
426
+ /* browser not supported */
427
+ function() {
428
+ return null
429
+ }
430
+
431
+ )()
432
+
433
+ }
434
+
435
+ , replaceSelection: function(text) {
436
+
437
+ var e = this.$textarea[0]
438
+
439
+ return (
440
+
441
+ ('selectionStart' in e && function() {
442
+ e.value = e.value.substr(0, e.selectionStart) + text + e.value.substr(e.selectionEnd, e.value.length)
443
+ // Set cursor to the last replacement end
444
+ e.selectionStart = e.value.length
445
+ return this
446
+ }) ||
447
+
448
+ /* browser not supported */
449
+ function() {
450
+ e.value += text
451
+ return jQuery(e)
452
+ }
453
+
454
+ )()
455
+
456
+ }
457
+
458
+ , getNextTab: function() {
459
+ // Shift the nextTab
460
+ if (this.$nextTab.length == 0) {
461
+ return null
462
+ } else {
463
+ var nextTab, tab = this.$nextTab.shift()
464
+
465
+ if (typeof tab == 'function') {
466
+ nextTab = tab()
467
+ } else if (typeof tab == 'object' && tab.length > 0) {
468
+ nextTab = tab
469
+ }
470
+
471
+ return nextTab
472
+ }
473
+ }
474
+
475
+ , setNextTab: function(start,end) {
476
+ // Push new selection into nextTab collections
477
+ if (typeof start == 'string') {
478
+ var that = this
479
+ this.$nextTab.push(function(){
480
+ return that.findSelection(start)
481
+ })
482
+ } else if (typeof start == 'numeric' && typeof end == 'numeric') {
483
+ var oldSelection = this.getSelection()
484
+
485
+ this.setSelection(start,end)
486
+ this.$nextTab.push(this.getSelection())
487
+
488
+ this.setSelection(oldSelection.start,oldSelection.end)
489
+ }
490
+
491
+ return
492
+ }
493
+
494
+ , enableButtons: function(name) {
495
+ var alter = function (el) {
496
+ el.removeAttr('disabled')
497
+ }
498
+
499
+ this.__alterButtons(name,alter)
500
+
501
+ return this
502
+ }
503
+
504
+ , disableButtons: function(name) {
505
+ var alter = function (el) {
506
+ el.attr('disabled','disabled')
507
+ }
508
+
509
+ this.__alterButtons(name,alter)
510
+
511
+ return this
512
+ }
513
+
514
+ , eventSupported: function(eventName) {
515
+ var isSupported = eventName in this.$element
516
+ if (!isSupported) {
517
+ this.$element.setAttribute(eventName, 'return;')
518
+ isSupported = typeof this.$element[eventName] === 'function'
519
+ }
520
+ return isSupported
521
+ }
522
+
523
+ , keydown: function (e) {
524
+ this.suppressKeyPressRepeat = ~$.inArray(e.keyCode, [40,38,9,13,27])
525
+ this.keyup(e)
526
+ }
527
+
528
+ , keypress: function (e) {
529
+ if (this.suppressKeyPressRepeat) return
530
+ this.keyup(e)
531
+ }
532
+
533
+ , keyup: function (e) {
534
+ var blocked = false
535
+ switch(e.keyCode) {
536
+ case 40: // down arrow
537
+ case 38: // up arrow
538
+ case 16: // shift
539
+ case 17: // ctrl
540
+ case 18: // alt
541
+ break
542
+
543
+ case 9: // tab
544
+ var nextTab
545
+ if (nextTab = this.getNextTab(),nextTab != null) {
546
+ // Get the nextTab if exists
547
+ var that = this
548
+ setTimeout(function(){
549
+ that.setSelection(nextTab.start,nextTab.end)
550
+ },500)
551
+ } else {
552
+ // Put the cursor to the end
553
+ this.setSelection(this.getContent().length,this.getContent().length)
554
+ }
555
+
556
+ blocked = true
557
+ break
558
+
559
+ case 13: // enter
560
+ case 27: // escape
561
+ blocked = false
562
+ break
563
+
564
+ default:
565
+ blocked = false
566
+ }
567
+
568
+ if (blocked) {
569
+ e.stopPropagation()
570
+ e.preventDefault()
571
+ }
572
+ }
573
+
574
+ , focus: function (e) {
575
+ var options = this.$options,
576
+ isHideable = options.hideable,
577
+ editor = this.$editor
578
+
579
+ editor.addClass('active')
580
+
581
+ // Blur other markdown(s)
582
+ $(document).find('.md-editor').each(function(){
583
+ if ($(this).attr('id') != editor.attr('id')) {
584
+ var attachedMarkdown
585
+
586
+ if (attachedMarkdown = $(this).find('textarea').data('markdown'),
587
+ attachedMarkdown == null) {
588
+ attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
589
+ }
590
+
591
+ if (attachedMarkdown) {
592
+ attachedMarkdown.blur()
593
+ }
594
+ }
595
+ })
596
+
597
+ return this
598
+ }
599
+
600
+ , blur: function (e) {
601
+ var options = this.$options,
602
+ isHideable = options.hideable,
603
+ editor = this.$editor,
604
+ editable = this.$editable
605
+
606
+ // Force to quit preview mode
607
+ if (this.$isPreview) {
608
+ this.hidePreview()
609
+ }
610
+
611
+ if (editor.hasClass('active') || this.$element.parent().length == 0) {
612
+ editor.removeClass('active')
613
+
614
+ if (isHideable) {
615
+
616
+ // Check for editable elements
617
+ if (editable.el != null) {
618
+ // Build the original element
619
+ var oldElement = $('<'+editable.type+'/>'),
620
+ content = this.getContent(),
621
+ currentContent = (typeof markdown == 'object') ? markdown.toHTML(content) : content
622
+
623
+ $(editable.attrKeys).each(function(k,v) {
624
+ oldElement.attr(editable.attrKeys[k],editable.attrValues[k])
625
+ })
626
+
627
+ // Get the editor content
628
+ oldElement.html(currentContent)
629
+
630
+ editor.replaceWith(oldElement)
631
+ } else {
632
+ editor.hide()
633
+
634
+ }
635
+ }
636
+
637
+ // Trigger the onBlur hook
638
+ options.onBlur(this)
639
+ }
640
+
641
+ return this
642
+ }
643
+
644
+ }
645
+
646
+ /* MARKDOWN PLUGIN DEFINITION
647
+ * ========================== */
648
+
649
+ var old = $.fn.markdown
650
+
651
+ $.fn.markdown = function (option) {
652
+ return this.each(function () {
653
+ var $this = $(this)
654
+ , data = $this.data('markdown')
655
+ , options = typeof option == 'object' && option
656
+ if (!data) $this.data('markdown', (data = new Markdown(this, options)))
657
+ })
658
+ }
659
+
660
+ $.fn.markdown.defaults = {
661
+ /* Editor Properties */
662
+ autofocus: false,
663
+ hideable: false,
664
+ savable:false,
665
+ width: 'inherit',
666
+ height: 'inherit',
667
+
668
+ /* Buttons Properties */
669
+ buttons: [
670
+ [{
671
+ name: 'groupFont',
672
+ data: [{
673
+ name: 'cmdBold',
674
+ title: 'Bold',
675
+ icon: 'icon icon-bold',
676
+ callback: function(e){
677
+ // Give/remove ** surround the selection
678
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
679
+
680
+ if (selected.length == 0) {
681
+ // Give extra word
682
+ chunk = 'strong text'
683
+ } else {
684
+ chunk = selected.text
685
+ }
686
+
687
+ // transform selection and set the cursor into chunked text
688
+ if (content.substr(selected.start-2,2) == '**'
689
+ && content.substr(selected.end,2) == '**' ) {
690
+ e.setSelection(selected.start-2,selected.end+2)
691
+ e.replaceSelection(chunk)
692
+ cursor = selected.start-2
693
+ } else {
694
+ e.replaceSelection('**'+chunk+'**')
695
+ cursor = selected.start+2
696
+ }
697
+
698
+ // Set the cursor
699
+ e.setSelection(cursor,cursor+chunk.length)
700
+ }
701
+ },{
702
+ name: 'cmdItalic',
703
+ title: 'Italic',
704
+ icon: 'icon icon-italic',
705
+ callback: function(e){
706
+ // Give/remove * surround the selection
707
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
708
+
709
+ if (selected.length == 0) {
710
+ // Give extra word
711
+ chunk = 'emphasized text'
712
+ } else {
713
+ chunk = selected.text
714
+ }
715
+
716
+ // transform selection and set the cursor into chunked text
717
+ if (content.substr(selected.start-1,1) == '*'
718
+ && content.substr(selected.end,1) == '*' ) {
719
+ e.setSelection(selected.start-1,selected.end+1)
720
+ e.replaceSelection(chunk)
721
+ cursor = selected.start-1
722
+ } else {
723
+ e.replaceSelection('*'+chunk+'*')
724
+ cursor = selected.start+1
725
+ }
726
+
727
+ // Set the cursor
728
+ e.setSelection(cursor,cursor+chunk.length)
729
+ }
730
+ },{
731
+ name: 'cmdHeading',
732
+ title: 'Heading',
733
+ icon: 'icon icon-font',
734
+ callback: function(e){
735
+ // Append/remove ### surround the selection
736
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent(), pointer, prevChar
737
+
738
+ if (selected.length == 0) {
739
+ // Give extra word
740
+ chunk = 'heading text'
741
+ } else {
742
+ chunk = selected.text
743
+ }
744
+
745
+ // transform selection and set the cursor into chunked text
746
+ if ((pointer = 4, content.substr(selected.start-pointer,pointer) == '### ')
747
+ || (pointer = 3, content.substr(selected.start-pointer,pointer) == '###')) {
748
+ e.setSelection(selected.start-pointer,selected.end)
749
+ e.replaceSelection(chunk)
750
+ cursor = selected.start-pointer
751
+ } else if (prevChar = content.substr(selected.start-1,1), !!prevChar && prevChar != '\n') {
752
+ e.replaceSelection('\n\n### '+chunk+'\n')
753
+ cursor = selected.start+6
754
+ } else {
755
+ // Empty string before element
756
+ e.replaceSelection('### '+chunk+'\n')
757
+ cursor = selected.start+4
758
+ }
759
+
760
+ // Set the cursor
761
+ e.setSelection(cursor,cursor+chunk.length)
762
+ }
763
+ }]
764
+ },{
765
+ name: 'groupLink',
766
+ data: [{
767
+ name: 'cmdUrl',
768
+ title: 'URL/Link',
769
+ icon: 'icon icon-globe',
770
+ callback: function(e){
771
+ // Give [] surround the selection and prepend the link
772
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
773
+
774
+ if (selected.length == 0) {
775
+ // Give extra word
776
+ chunk = 'enter link description here'
777
+ } else {
778
+ chunk = selected.text
779
+ }
780
+
781
+ link = prompt('Insert Hyperlink','http://')
782
+
783
+ if (link != null) {
784
+ // transform selection and set the cursor into chunked text
785
+ e.replaceSelection('['+chunk+']('+link+')')
786
+ cursor = selected.start+1
787
+
788
+ // Set the cursor
789
+ e.setSelection(cursor,cursor+chunk.length)
790
+ }
791
+ }
792
+ },{
793
+ name: 'cmdImage',
794
+ title: 'Image',
795
+ icon: 'icon icon-picture',
796
+ callback: function(e){
797
+ // Give ![] surround the selection and prepend the image link
798
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent(), link
799
+
800
+ if (selected.length == 0) {
801
+ // Give extra word
802
+ chunk = 'enter image description here'
803
+ } else {
804
+ chunk = selected.text
805
+ }
806
+
807
+ link = prompt('Insert Image Hyperlink','http://')
808
+
809
+ if (link != null) {
810
+ // transform selection and set the cursor into chunked text
811
+ e.replaceSelection('!['+chunk+']('+link+' "enter image title here")')
812
+ cursor = selected.start+2
813
+
814
+ // Set the next tab
815
+ e.setNextTab('enter image title here')
816
+
817
+ // Set the cursor
818
+ e.setSelection(cursor,cursor+chunk.length)
819
+ }
820
+ }
821
+ }]
822
+ },{
823
+ name: 'groupMisc',
824
+ data: [{
825
+ name: 'cmdList',
826
+ title: 'List',
827
+ icon: 'icon icon-list',
828
+ callback: function(e){
829
+ // Prepend/Give - surround the selection
830
+ var chunk, cursor, selected = e.getSelection(), content = e.getContent()
831
+
832
+ // transform selection and set the cursor into chunked text
833
+ if (selected.length == 0) {
834
+ // Give extra word
835
+ chunk = 'list text here'
836
+
837
+ e.replaceSelection('- '+chunk)
838
+
839
+ // Set the cursor
840
+ cursor = selected.start+2
841
+ } else {
842
+ if (selected.text.indexOf('\n') < 0) {
843
+ chunk = selected.text
844
+
845
+ e.replaceSelection('- '+chunk)
846
+
847
+ // Set the cursor
848
+ cursor = selected.start+2
849
+ } else {
850
+ var list = []
851
+
852
+ list = selected.text.split('\n')
853
+ chunk = list[0]
854
+
855
+ $.each(list,function(k,v) {
856
+ list[k] = '- '+v
857
+ })
858
+
859
+ e.replaceSelection('\n\n'+list.join('\n'))
860
+
861
+ // Set the cursor
862
+ cursor = selected.start+4
863
+ }
864
+ }
865
+
866
+
867
+
868
+ // Set the cursor
869
+ e.setSelection(cursor,cursor+chunk.length)
870
+ }
871
+ }]
872
+ },{
873
+ name: 'groupUtil',
874
+ data: [{
875
+ name: 'cmdPreview',
876
+ title: 'Preview',
877
+ btnText: 'Preview',
878
+ btnClass: 'btn btn-inverse',
879
+ icon: 'icon icon-white icon-search',
880
+ callback: function(e){
881
+ // Check the preview mode and toggle based on this flag
882
+ var isPreview = e.$isPreview,content
883
+
884
+ if (isPreview == false) {
885
+ // Give flag that tell the editor enter preview mode
886
+ e.showPreview()
887
+ } else {
888
+ e.hidePreview()
889
+ }
890
+ }
891
+ }]
892
+ }]
893
+ ],
894
+ additionalButtons:[], // Place to hook more buttons by code
895
+
896
+ /* Events hook */
897
+ onShow: function (e) {},
898
+ onPreview: function (e) {},
899
+ onSave: function (e) {},
900
+ onBlur: function (e) {}
901
+ }
902
+
903
+ $.fn.markdown.Constructor = Markdown
904
+
905
+
906
+ /* MARKDOWN NO CONFLICT
907
+ * ==================== */
908
+
909
+ $.fn.markdown.noConflict = function () {
910
+ $.fn.markdown = old
911
+ return this
912
+ }
913
+
914
+ /* MARKDOWN GLOBAL FUNCTION & DATA-API
915
+ * ==================================== */
916
+ var initMarkdown = function(el) {
917
+ var $this = el
918
+
919
+ if ($this.data('markdown')) {
920
+ $this.data('markdown').showEditor()
921
+ return
922
+ }
923
+ $this.markdown($this.data())
924
+ }
925
+
926
+ var analyzeMarkdown = function(e) {
927
+ var blurred = false,
928
+ el,
929
+ $docEditor = $(e.currentTarget)
930
+
931
+ // Check whether it was editor childs or not
932
+ if ((e.type == 'focusin' || e.type == 'click') && $docEditor.length == 1 && typeof $docEditor[0] == 'object'){
933
+ el = $docEditor[0].activeElement
934
+ if ( ! $(el).data('markdown')) {
935
+ if (typeof $(el).parent().parent().parent().attr('class') == "undefined"
936
+ || $(el).parent().parent().parent().attr('class').indexOf('md-editor') < 0) {
937
+ if ( typeof $(el).parent().parent().attr('class') == "undefined"
938
+ || $(el).parent().parent().attr('class').indexOf('md-editor') < 0) {
939
+
940
+ blurred = true
941
+ }
942
+ } else {
943
+ blurred = false
944
+ }
945
+ }
946
+
947
+
948
+ if (blurred) {
949
+ // Blur event
950
+ $(document).find('.md-editor').each(function(){
951
+ var parentMd = $(el).parent()
952
+
953
+ if ($(this).attr('id') != parentMd.attr('id')) {
954
+ var attachedMarkdown
955
+
956
+ if (attachedMarkdown = $(this).find('textarea').data('markdown'),
957
+ attachedMarkdown == null) {
958
+ attachedMarkdown = $(this).find('div[data-provider="markdown-preview"]').data('markdown')
959
+ }
960
+
961
+ if (attachedMarkdown) {
962
+ attachedMarkdown.blur()
963
+ }
964
+ }
965
+ })
966
+ }
967
+
968
+ e.stopPropagation()
969
+ }
970
+ }
971
+
972
+ $(document)
973
+ .on('click.markdown.data-api', '[data-provide="markdown-editable"]', function (e) {
974
+ initMarkdown($(this))
975
+ e.preventDefault()
976
+ })
977
+ .on('click', function (e) {
978
+ analyzeMarkdown(e)
979
+ })
980
+ .on('focusin', function (e) {
981
+ analyzeMarkdown(e)
982
+ })
983
+ .ready(function(){
984
+ $('textarea[data-provide="markdown"]').each(function(){
985
+ initMarkdown($(this))
986
+ })
987
+ })
988
+
989
+ }(window.jQuery);